const time = Date.now || function () {
    return +new Date()
  }
  
  let running = {}
  let counter = 1
  let desiredFrames = 60
  let millisecondsPerSecond = 1000
  
  // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
  
  // requestAnimationFrame polyfill by Erik Möller
  // fixes from Paul Irish and Tino Zijdel
  ;(function () {
    var lastTime = 0
    var vendors = ['ms', 'moz', 'webkit', 'o']
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
      window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
      window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']
    }
  
    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback, element) {
        var currTime = new Date().getTime()
        var timeToCall = Math.max(0, 16 - (currTime - lastTime))
        var id = window.setTimeout(function () { callback(currTime + timeToCall) },
              timeToCall)
        lastTime = currTime + timeToCall
        return id
      }
    }
    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id)
      }
    }
  }())
  
  export default {
  
    // A requestAnimationFrame wrapper / polyfill.
    requestAnimationFrame: (function () {
      var requestFrame = window.requestAnimationFrame
      return function (callback, root) {
        requestFrame(callback, root)
      }
    })(),
  
    // Stops the given animation.
    stop (id) {
      var cleared = running[id] != null
      if (cleared) {
        running[id] = null
      }
      return cleared
    },
  
    // Whether the given animation is still running.
    isRunning (id) {
      return running[id] != null
    },
  
    // Start the animation.
    start (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
      var _this = this
      var start = time()
      var lastFrame = start
      var percent = 0
      var dropCounter = 0
      var id = counter++
  
      if (!root) {
        root = document.body
      }
  
      // Compacting running db automatically every few new animations
      if (id % 20 === 0) {
        var newRunning = {}
        for (var usedId in running) {
          newRunning[usedId] = true
        }
        running = newRunning
      }
  
      // This is the internal step method which is called every few milliseconds
      var step = function (virtual) {
        // Normalize virtual value
        var render = virtual !== true
        // Get current time
        var now = time()
  
        // Verification is executed before next animation step
        if (!running[id] || (verifyCallback && !verifyCallback(id))) {
          running[id] = null
          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false)
          return
        }
  
        // For the current rendering to apply let's update omitted steps in memory.
        // This is important to bring internal state variables up-to-date with progress in time.
        if (render) {
          var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1
          for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
            step(true)
            dropCounter++
          }
        }
  
        // Compute percent value
        if (duration) {
          percent = (now - start) / duration
          if (percent > 1) {
            percent = 1
          }
        }
  
        // Execute step callback, then...
        var value = easingMethod ? easingMethod(percent) : percent
        if ((stepCallback(value, now, render) === false || percent === 1) && render) {
          running[id] = null
          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null)
        } else if (render) {
          lastFrame = now
          _this.requestAnimationFrame(step, root)
        }
      }
  
      // Mark as running
      running[id] = true
      // Init first step
      _this.requestAnimationFrame(step, root)
      // Return unique animation ID
      return id
    }
  }
  